#ifndef     __CAN_BUS_H
#define     __CAN_BUS_H

#include "../utils/nocopyable.h"
#include "../utils/object.h"
#include "../rtos/message_queue.h"
#include "../rtos/event_flags.h"
#include "../conn/can_frame.h"
#include "../conn/conn_info.h"
#include "../utils/error.h"
#include "../conn/conn_type.h"

namespace driver {

class CANBus : private utils::NoCopyable {

public:
    static constexpr uint32_t FLAG_FRAME_COMPLETE = 0x01;
    static constexpr uint32_t FLAG_PKG_LOCK = 0x02;
    static constexpr uint32_t FLAG_PKG_ACK_LOCK = 0x04;
    static constexpr uint32_t FLAG_RECV_ACK = 0x08;

    CANBus();

    inline uint8_t status() { return m_can_ec; }
    
    inline bool poll(conn::CANFrame &frame, uint32_t timeout = osWaitForever) {
        return m_mq.poll(frame, timeout) == osOK;
    }

    inline void write_std_frame(uint16_t id, const uint8_t *buf8, uint8_t len) {
        uint32_t tir = (id << 21) + 0x01;
        write_frame(tir, buf8, len);
    }

    void write_ext_pkg(const conn::ConnInfo &conn_info, 
                    const uint8_t *buf_align8, 
                    uint8_t len);
    
    utils::Error write_ext_pkg_wait_ack(const conn::ConnInfo &conn_info, 
                    const uint8_t *buf_align8, 
                    uint8_t len);

    void write_ack(uint8_t dest, uint8_t seq);

    inline void on_recv_ack() { m_flags.set_flags(FLAG_RECV_ACK); }

    void on_frame_recv();
    void notify_frame_tx_complete();

#if defined(BOARD_ARM)
    inline bool poll_arm(conn::CANFrame &frame, uint32_t timeout = osWaitForever) {
        return m_arm_mq.poll(frame, timeout) == osOK;
    }
#endif

private:
    void write_frame(uint32_t tir, const uint8_t *buf8, uint8_t len);
    void write_empty_frame(uint32_t tir);

    inline void wait_frame_tx_complete() {
        m_flags.wait_any_flags(FLAG_FRAME_COMPLETE);
    }

    inline void write_ext_frame(uint32_t id, const uint8_t *buf8, uint8_t len) {
        uint32_t tir = (id << 3) + 0x04 + 0x01;
        write_frame(tir, buf8, len);
    }

    inline void write_ext_empty_frame(uint32_t id) {
        write_empty_frame((id << 3) + 0x04 + 0x01);
    }

    inline bool wait_ack() {
        return 0x00 == (osFlagsError & m_flags.wait_any_flags(FLAG_RECV_ACK, 500));
    }

    inline void clear_ack() {
        m_flags.clr_flags(FLAG_RECV_ACK);
    }

    inline void pkg_lock() {
        m_flags.wait_any_flags(FLAG_PKG_LOCK);
    }
    inline void pkg_unlock() {
        m_flags.set_flags(FLAG_PKG_LOCK);
    }

    inline void pkg_ack_lock() {
        m_flags.wait_any_flags(FLAG_PKG_ACK_LOCK);
    }
    inline void pkg_ack_unlock() {
        m_flags.set_flags(FLAG_PKG_ACK_LOCK);
    }
    
    rtos::MessageQueue<conn::CANFrame, 50> m_mq;
    rtos::EventFlags m_flags;
    uint8_t m_can_ec = 0;
#if defined(BOARD_ARM)
    rtos::MessageQueue<conn::CANFrame, 1> m_arm_mq;
#endif

};

inline utils::Object<CANBus> canBus;

}


#endif
